package com.geor.gcv.core.mat;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

import org.nutz.log.Loggers;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;
import org.slf4j.Logger;

import com.geor.gcv.core.model.Area;
import com.geor.gcv.core.process.GrayProcess;
import com.geor.gcv.core.process.InputProcess;
import com.geor.gcv.core.process.ResizeProcess;
import com.geor.gcv.core.process.RotateProcess;

/**
 * 在图片中有条形码的情况下，通过对条形码的分析，得到图片倾斜角度
 * 
 * @author geor.lusongnan
 *
 */
public class TiltHanderImpl implements TiltHander {
	static Logger log = Loggers.get();

	public TiltHanderImpl() {
	}

	@Override
	public TiltWapper excute(Mat mat) {
		// 首先根据原图定位条形码位置
		AreaWapper aw = locateTheBarcode(mat);
		List<Area> areas = aw.areas;
		Collections.sort(areas, new AreaComparator());

		// 其次旋转90度 再次定位条形码的位置
		Mat mat90 = new RotateProcess(mat, 90).process();
		AreaWapper aw90 = locateTheBarcode(mat90);
		List<Area> areas90 = aw90.areas;
		Collections.sort(areas90, new AreaComparator());

		// 根据上面的结果得出最大的区域，也就是条形码的区域
		Area temp = null;
		float angle = 0.0f;
		Mat effctTmp = null;

		// 因为要根据条形码的区域来计算出图片的倾斜角度，所以必须锁定膨胀之后的快递单效果图
		if (areas.isEmpty() && areas90.isEmpty()) {
			boolean upsideDown = reversal(mat, 0.0f);
			Mats.releseMat(mat90);
			Mats.releseMat(mat);
			Mats.releseMat(aw.effct);
			Mats.releseMat(aw90.effct);
			return new TiltWapper(0.0f, upsideDown);
		} else if (areas.isEmpty() && !areas90.isEmpty()) {
			temp = areas90.get(0);
			effctTmp = aw90.effct;
		} else if (!areas.isEmpty() && areas90.isEmpty()) {
			temp = areas.get(0);
			effctTmp = aw.effct;
		} else {
			Area area1 = areas.get(0);
			Area area2 = areas90.get(0);
			if ((long) area1.getHeight() * area1.getWidth() > (long) area2.getHeight() * area2.getWidth()) {
				temp = area1;
				effctTmp = aw.effct;
			} else {
				temp = area2;
				angle = 90.0f;
				effctTmp = aw90.effct;
			}
		}

		// 得到条形码，膨胀的效果图
		effctTmp = Mats.screenShoot(effctTmp, temp);
		// 下面就是计算倾斜角度
		// 中心
		int centerCol = effctTmp.cols() / 2;
		int leftCol = centerCol - centerCol / 2;
		int rightCol = centerCol + centerCol / 2;

		int leftRow = 0;
		for (int row = 0; row < effctTmp.rows(); row++) {
			int pex = (int) effctTmp.get(row, leftCol)[0];
			if (pex > 100) {
				leftRow = row;
				break;
			}
		}

		int centerRow = 0;
		for (int row = 0; row < effctTmp.rows(); row++) {
			int pex = (int) effctTmp.get(row, centerCol)[0];
			if (pex > 100) {
				centerRow = row;
				break;
			}
		}

		int rightRow = 0;
		for (int row = 0; row < effctTmp.rows(); row++) {
			int pex = (int) effctTmp.get(row, rightCol)[0];
			if (pex > 100) {
				rightRow = row;
				break;
			}
		}
		log.debug("left row:" + leftRow);
		log.debug("center row:" + centerRow);
		log.debug("right row:" + rightRow);

		// 计算左边直线的角度
		int leftHeight = leftRow - centerRow;
		int leftWidth = centerCol - leftCol;
		if (leftWidth <= 1) {
			boolean upsideDown = reversal(mat, 0.0f);
			Mats.releseMat(mat90);
			Mats.releseMat(mat);
			Mats.releseMat(aw.effct);
			Mats.releseMat(aw90.effct);
			return new TiltWapper(0.0f, upsideDown);
		}
		float leftAngle = (float) (180 / Math.PI * Math.atan((float) leftHeight / leftWidth));
		log.debug("left angle:" + leftAngle);
		// 计算右边直线的角度
		int rightHeight = centerRow - rightRow;
		int rightWidth = rightCol - centerCol;
		if (rightWidth <= 1) {
			boolean upsideDown = reversal(mat, 0.0f);
			Mats.releseMat(mat90);
			Mats.releseMat(mat);
			Mats.releseMat(aw.effct);
			Mats.releseMat(aw90.effct);
			return new TiltWapper(0.0f, upsideDown);
		}
		float rightAngle = (float) (180 / Math.PI * Math.atan((float) rightHeight / rightWidth));
		log.debug("right angle:" + rightAngle);

		// 判断倾斜的角度是否合理
		if (Math.abs(rightAngle - leftAngle) > 10.0f || rightAngle == 0.0f || leftAngle == 0.0f) {
			boolean upsideDown = reversal(mat, 0.0f);
			Mats.releseMat(mat90);
			Mats.releseMat(mat);
			Mats.releseMat(aw.effct);
			Mats.releseMat(aw90.effct);
			return new TiltWapper(0.0f, upsideDown);
		}

		// 简单判断是否是倒着的，不准
		boolean upsideDown = reversal(mat, (leftAngle + rightAngle) / 2 + angle);
		Mats.releseMat(mat90);
		Mats.releseMat(mat);
		Mats.releseMat(aw.effct);
		Mats.releseMat(aw90.effct);
		return new TiltWapper((leftAngle + rightAngle) / 2 + angle, upsideDown);
	}

	private boolean reversal(Mat mat, float jiaodu) {
		try {
			Mat img = new RotateProcess(new InputProcess(mat), jiaodu).process();
			AreaWapper awnow = locateTheBarcode(img);
			Collections.sort(awnow.areas, new AreaComparator());

			Area area1 = null;
			Iterator<Area> it = awnow.areas.iterator();
			while (it.hasNext()) {
				area1 = it.next();
				if (area1.getHeight() < area1.getWidth()) {
					area1 = area1.expandAny(5);
					break;
				}
			}
			// 没有合适的条形码区域
			if (area1 == null)
				return false;

			Mat effcet = area1 == null ? null : Mats.screenShoot(img, area1.expand(1 / scale));

			if (effcet != null) {
				// TODO 是否颠倒
				Mat bin1 = Mats.bin(effcet, 21, 30);
				int start = 5;
				int end = bin1.rows() - start;

				List<Integer> start_rows = new ArrayList<Integer>();
				start_rows.add(0);
				start_rows.add(start);

				List<Integer> end_rows = new ArrayList<Integer>();
				end_rows.add(end);
				end_rows.add(bin1.rows() - 1);

				int sum1 = Mats.getCols(bin1, start_rows).size();
				int sum2 = Mats.getCols(bin1, end_rows).size();
				if (sum1 > sum2) {
					Mats.releseMat(awnow.effct);
					Mats.releseMat(img);
					return true;
				}
			}
			Mats.releseMat(awnow.effct);
			Mats.releseMat(img);
			return false;
		} catch (Exception e) {
			// 这边异常不影响结果，处理图像是有些细节没处理，直接抛出
			return false;
		}
	}

	private float scale = 0.6f;

	private AreaWapper locateTheBarcode(Mat mat) {
		// 调整大小提高效率
		mat = new ResizeProcess(new InputProcess(mat), scale).process();
		// 灰度图片
		Mat gray = new GrayProcess(new InputProcess(mat)).process();
		// 高斯滤波
		Mat dst = new Mat();
		Imgproc.GaussianBlur(gray, dst, new Size(5, 5), 0);
		Mats.releseMat(gray);

		// Sobel求得水平和垂直方向灰度图像的梯度差,使用Sobel算子
		Mat dst_x = new Mat();
		Mat abs_dst_x = new Mat();
		int scale = 1;
		int delta = 0;
		Imgproc.Sobel(dst, dst_x, CvType.CV_16S, 1, 0, 3, scale, delta, Imgproc.BORDER_DEFAULT);
		Core.convertScaleAbs(dst_x, abs_dst_x, 1, 0);

		Mat dst_y = new Mat();
		Mat abs_dst_y = new Mat();
		Imgproc.Sobel(dst, dst_y, CvType.CV_16S, 0, 1, 3, scale, delta, Imgproc.BORDER_DEFAULT);
		Core.convertScaleAbs(dst_y, abs_dst_y, 1, 0);

		Mat result_gary = new Mat();
		Core.subtract(abs_dst_x, abs_dst_y, result_gary);
		Mats.releseMat(dst_x);
		Mats.releseMat(abs_dst_x);
		Mats.releseMat(dst_y);
		Mats.releseMat(abs_dst_x);
		Mats.releseMat(dst);

		// 均值滤波，消除高频噪声
		Mat avg = new Mat();
		Imgproc.blur(result_gary, avg, new Size(5, 5), new Point(-1, -1));
		Mats.releseMat(result_gary);

		// 二值化
		Mat bin = new Mat(avg.size(), CvType.CV_8UC1);
		for (int row = 0; row < avg.rows(); row++) {
			for (int col = 0; col < avg.cols(); col++) {
				int pex = (int) avg.get(row, col)[0];
				if (pex > 40) {
					bin.put(row, col, 255);
				} else {
					bin.put(row, col, 0);
				}
			}
		}
		Mats.releseMat(avg);
		// 闭运算
		Mat close = new Mat();
		Mat close_m = new Mat(3, 3, CvType.CV_8UC1);
		Imgproc.morphologyEx(bin, close, Imgproc.MORPH_CLOSE, close_m, new Point(-1, -1), 1);
		Mats.releseMat(bin);
		Mats.releseMat(close_m);
		// 腐蚀，去除孤立的点
		Mat fus = new Mat();
		Mat fus_m = new Mat(10, 10, CvType.CV_8U);
		Imgproc.erode(close, fus, fus_m, new Point(-1, -1), 2);
		Mats.releseMat(fus_m);
		Mats.releseMat(close);

		// 膨胀，填充条形码间空隙，根据核的大小，有可能需要2~3次膨胀操作
		Mat pz = new Mat();
		Mat pz_m = new Mat(12, 12, CvType.CV_8U);
		Imgproc.dilate(fus, pz, pz_m);
		Imgproc.dilate(pz, pz, pz_m);
		Imgproc.dilate(pz, pz, pz_m);
		Mats.releseMat(fus);
		Mats.releseMat(pz_m);

		// 通过findContours找到条形码区域的矩形边界
		List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
		Mat pzTmp = pz.clone();
		try {
			Imgproc.findContours(pzTmp, contours, new Mat(), Imgproc.RETR_CCOMP, Imgproc.CHAIN_APPROX_NONE);
		} catch (Exception e) {
		}

		List<Area> areas = new ArrayList<Area>();

		for (MatOfPoint point : contours) {
			if (point.rows() != 0 && point.cols() != 0) {
				Rect rect = Imgproc.boundingRect(point);
				areas.add(new Area().toAreaByRect(rect));
				Mats.releseMat(point);
			}
		}
		Mats.releseMat(pzTmp);
		return new AreaWapper(pz, areas);
	}

	class AreaWapper {
		public Mat effct;
		public List<Area> areas;

		public AreaWapper(Mat effct, List<Area> areas) {
			super();
			this.effct = effct;
			this.areas = areas;
		}
	}

	class AreaComparator implements Comparator<Area> {

		@Override
		public int compare(Area area1, Area area2) {
			long mj1 = (long) area1.getHeight() * area1.getWidth();
			long mj2 = (long) area2.getWidth() * area2.getHeight();

			if (mj1 > mj2) {
				return -1;
			}

			if (mj1 < mj2) {
				return 1;
			}

			return 0;
		}

	}

}
